package com.google.code.mochaccino.framework.rest.resources;
/*
 * Copyright 2012 Claude Houle claude.houle@gmail.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import com.google.code.mochaccino.framework.rest.cache.CacheHelper;
import com.google.code.mochaccino.framework.rest.mime.MimeType;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Date;
import java.util.Hashtable;
import java.util.Map;
import javax.inject.Inject;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.StreamingOutput;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/** Resource Service */
public class ResourceService {
	/** The Mime Types */
	private Map<String,String> mimeTypes = new Hashtable<String,String>();

	/** The Resource Cache Helper */
	private CacheHelper cacheHelper = null;

	/** The Resource Loader */
	private ResourceLoader resourceLoader = null;

	/** The Default Mime Type */
	private String defaultMimeType = MediaType.APPLICATION_OCTET_STREAM;

	/** Use Default Mime Type */
	private boolean useDefaultMimeType = false;

	/** The Logger */
	protected Logger logger = LoggerFactory.getLogger( getClass() );

	/** Default Constructor */
	@Inject
	public ResourceService( ResourceLoader resourceLoader ) {
		this.resourceLoader = resourceLoader;
	}

	/** Add a mime-type */
	public final boolean addMimeType( String extension, String mimeType ) {
		boolean replace = mimeTypes.containsKey( extension );
		mimeTypes.put( extension, mimeType );
		return replace;
	}

	/** Add Mime Type */
	public final boolean addMimeType( MimeType mime ) {
		boolean replace = mimeTypes.containsKey( mime.getMimeType() );
		mimeTypes.put( mime.getExtension(), mime.getMimeType() );
		return replace;
	}

	/** Clear all mime-types for a custom list. */
	protected final void clearMimeTypes() {
		mimeTypes.clear();
	}

	/** Return the Resource Cache */
	public CacheHelper getCacheHelper() {
		return cacheHelper;
	}

	/** Set the Resource Cache */
	public void setCacheHelper( CacheHelper cacheHelper ) {
		this.cacheHelper = cacheHelper;
	}

	/** Return the Default Mime Type */
	public final String getDefaultMimeType() {
		return defaultMimeType;
	}

	/** Set the Default Mime Type */
	public final void setDefaultMimeType( String defaultMimeType ) {
		this.defaultMimeType = defaultMimeType;
	}

	/** Return the Response for specified resource */
	public final Response getResource( Request request, String resourceId ) {
		// If the resource id is empty... Not found
		if ( resourceId.trim().length() == 0 ) {
			return Response.status( Status.NOT_FOUND ).build();
		}

		resourceId = cleanup( resourceId );

		try {
			// Try find the URLs has given by the end-user.
			final URL resourceURL = getURL( resourceId );
			if ( resourceURL == null ) {
				return Response.status( Status.NOT_FOUND ).build();
			}

			// Get the mime-type (if any). Use default when appropriate (Depends on configuration of this service)
			// If we have no mime-type, then it means that this endpoint was not meant to serve this type of resources.
			String mimeType = getMimeType( resourceId );
			if ( mimeType == null ) {
				return Response.status( Status.UNSUPPORTED_MEDIA_TYPE ).build();
			}

			// Check if the resource was modified since last theoritical access from the browser.
			File resourceFile = new File( resourceURL.getFile() );
			Date resourceLastModified = new Date( resourceFile.lastModified() );
			if ( cacheHelper != null ) {
				ResponseBuilder builder = cacheHelper.checkNotModified( request, resourceId, resourceLastModified );
				if ( builder != null ) {
					return builder.build();
				}
			}

			/*
			 * Build Response
			 */
			ResponseBuilder builder = Response.ok();
			builder.type( mimeType );
			builder.entity( new StreamingOutput() {
				@Override
				public void write( OutputStream out ) throws IOException, WebApplicationException {
					InputStream in = null;
					try {
						in = resourceURL.openStream();
						IOUtils.copy( in, out );
						out.flush();
					} finally {
						IOUtils.closeQuietly( in );
					}
				}
			} );

			if ( cacheHelper != null ) {
				cacheHelper.setResponseCacheMetadata( builder, resourceId, resourceLastModified );
			}

			return builder.build();
		} catch ( MalformedURLException e ) {
			logger.error( e.getMessage(), e );
			return Response.serverError().build();
		} catch ( IOException e ) {
			logger.error( e.getMessage(), e );
			return Response.serverError().build();
		}
	}

	protected final String cleanup( String path ) {
		try {
			File file = new File( "", path );
			path = file.getCanonicalPath();
		} catch ( IOException e ) {
			logger.debug( e.getMessage(), e );
		}
		StringBuilder buf = new StringBuilder();
		for ( int i = 0; i < path.length(); i++ ) {
			if ( path.charAt( i ) == '\0' ) {
				break;
			}
			buf.append( path.charAt( i ) );
		}
		return buf.toString().replace( "//", "/" );
	}

	/** Return the Mime type of the extension. */
	protected final String getMimeType( String resourceId ) {
		String extension = null;
		int idxExtension = resourceId.lastIndexOf( "." );
		if ( idxExtension == -1 ) {
			extension = "";
		} else {
			extension = resourceId.substring( idxExtension );
		}

		String mimeType = mimeTypes.get( extension );
		if ( useDefaultMimeType && defaultMimeType != null && mimeType == null ) {
			mimeType = defaultMimeType;
		}
		return mimeType;
	}

	/** Return the URLs for the specified path. */
	public URL getURL( String resourceId ) throws MalformedURLException {
		return resourceLoader.getResource( resourceId );
	}

	/** Use Default Mime Type if no compatible mime-type is found. */
	public final boolean isUseDefaultMimeType() {
		return useDefaultMimeType;
	}

	/** Set the use default mime-type flag */
	public final void setUseDefaultMimeType( boolean useDefaultMimeType ) {
		this.useDefaultMimeType = useDefaultMimeType;
	}
}
